package io.gitee.welson.sensitive.service;

import io.gitee.welson.sensitive.config.SensitiveExportProperties;
import io.gitee.welson.sensitive.enums.OverflowStrategy;
import io.gitee.welson.sensitive.exception.DataOverflowException;
import io.gitee.welson.sensitive.exception.ExportFailedException;
import io.gitee.welson.sensitive.model.ExportParams;
import io.gitee.welson.sensitive.model.SheetData;
import io.gitee.welson.sensitive.writer.ExportWriter;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

/**
 * 默认敏感数据导出服务实现
 *
 * @author welson
 */
public class DefaultSensitiveExportService implements SensitiveExportService {

    private final ExportWriter exportWriter;
    private final SensitiveExportProperties properties;

    public DefaultSensitiveExportService(ExportWriter exportWriter, SensitiveExportProperties properties) {
        this.exportWriter = exportWriter;
        this.properties = properties;
    }

    @Override
    public <T> void export(HttpServletResponse response, ExportParams params, List<T> data, Class<T> dataClass) {
        try {
            setExportResponse(response, params);
            doExportWithIterator(response.getOutputStream(), data, dataClass, params);
        } catch (IOException e) {
            throw new ExportFailedException("导出失败", e, -1, -1);
        }
    }

    @Override
    public <T> void export(HttpServletResponse response, String fileName, List<T> data, Class<T> dataClass) {
        ExportParams params = ExportParams.builder()
                .fileName(fileName)
                .build();
        export(response, params, data, dataClass);
    }

    @Override
    public <T> void exportMultiSheet(HttpServletResponse response, ExportParams params, List<SheetData<T>> sheetDataList, Class<T> dataClass) {
        try {
            setExportResponse(response, params);
            OutputStream outputStream = response.getOutputStream();
            doExportMultiSheet(outputStream, params, sheetDataList, dataClass);
        } catch (IOException e) {
            throw new ExportFailedException("多Sheet页导出失败", e, -1, -1);
        }
    }

    @Override
    public <T> void exportMultiSheet(HttpServletResponse response, String fileName, List<SheetData<T>> sheetDataList, Class<T> dataClass) {
        ExportParams params = ExportParams.builder()
                .fileName(fileName)
                .build();
        exportMultiSheet(response, params, sheetDataList, dataClass);
    }

    @Override
    public <T> void exportBatch(HttpServletResponse response, ExportParams params, DataSupplier<T> dataSupplier, int batchSize, Class<T> dataClass) {
        try {
            setExportResponse(response, params);
            OutputStream outputStream = response.getOutputStream();
            doExportBatch(outputStream, params, dataSupplier, batchSize, dataClass);
        } catch (IOException e) {
            throw new ExportFailedException("批量导出失败", e, -1, -1);
        }
    }

    @Override
    public <T> void exportBatch(HttpServletResponse response, String fileName, DataSupplier<T> dataSupplier, int batchSize, Class<T> dataClass) {
        ExportParams params = ExportParams.builder()
                .fileName(fileName)
                .build();
        exportBatch(response, params, dataSupplier, batchSize, dataClass);
    }

    @Override
    public <T> void exportWithIterator(HttpServletResponse response, ExportParams params, BatchDataIterator<T> iterator, Class<T> dataClass) {
        try {
            setExportResponse(response, params);
            OutputStream outputStream = response.getOutputStream();
            doExportWithIterator(outputStream, params, iterator, dataClass);
        } catch (IOException e) {
            throw new ExportFailedException("使用迭代器批量导出失败", e, -1, -1);
        }
    }

    @Override
    public <T> void exportWithIterator(HttpServletResponse response, String fileName, BatchDataIterator<T> iterator, Class<T> dataClass) {
        ExportParams params = ExportParams.builder()
                .fileName(fileName)
                .build();
        exportWithIterator(response, params, iterator, dataClass);
    }

    @Override
    public <T> void exportToFile(String filePath, ExportParams params, List<T> data, Class<T> dataClass) {
        try (OutputStream outputStream = Files.newOutputStream(Paths.get(filePath))) {
            doExportWithIterator(outputStream, data, dataClass, params);
        } catch (IOException e) {
            throw new ExportFailedException("导出到文件失败: " + filePath, e, -1, -1);
        }
    }

    @Override
    public <T> void exportToFile(String filePath, List<T> data, Class<T> dataClass) {
        Pair<String, String> fileNameAndSuffix = getFileNameAndSuffix(filePath);
        ExportParams params = ExportParams.builder()
                .fileName(fileNameAndSuffix.left)
                .suffix(fileNameAndSuffix.right)
                .build();
        exportToFile(filePath, params, data, dataClass);
    }

    @Override
    public <T> void exportMultiSheetToFile(String filePath, ExportParams params, List<SheetData<T>> sheetDataList, Class<T> dataClass) {
        try (OutputStream outputStream = Files.newOutputStream(Paths.get(filePath))) {
            doExportMultiSheet(outputStream, params, sheetDataList, dataClass);
        } catch (IOException e) {
            throw new ExportFailedException("多Sheet页导出到文件失败: " + filePath, e, -1, -1);
        }
    }

    @Override
    public <T> void exportMultiSheetToFile(String filePath, List<SheetData<T>> sheetDataList, Class<T> dataClass) {
        Pair<String, String> fileNameAndSuffix = getFileNameAndSuffix(filePath);
        ExportParams params = ExportParams.builder()
                .fileName(fileNameAndSuffix.left)
                .suffix(fileNameAndSuffix.right)
                .build();
        exportMultiSheetToFile(filePath, params, sheetDataList, dataClass);
    }

    @Override
    public <T> void exportBatchToFile(String filePath, ExportParams params, DataSupplier<T> dataSupplier, int batchSize, Class<T> dataClass) {
        try (OutputStream outputStream = Files.newOutputStream(Paths.get(filePath))) {
            doExportBatch(outputStream, params, dataSupplier, batchSize, dataClass);
        } catch (IOException e) {
            throw new ExportFailedException("批量导出到文件失败: " + filePath, e, -1, -1);
        }
    }

    @Override
    public <T> void exportBatchToFile(String filePath, DataSupplier<T> dataSupplier, int batchSize, Class<T> dataClass) {
        Pair<String, String> fileNameAndSuffix = getFileNameAndSuffix(filePath);
        ExportParams params = ExportParams.builder()
                .fileName(fileNameAndSuffix.left)
                .suffix(fileNameAndSuffix.right)
                .build();
        exportBatchToFile(filePath, params, dataSupplier, batchSize, dataClass);
    }

    @Override
    public <T> void exportWithIteratorToFile(String filePath, ExportParams params, BatchDataIterator<T> iterator, Class<T> dataClass) {
        try (OutputStream outputStream = Files.newOutputStream(Paths.get(filePath))) {
            doExportWithIterator(outputStream, params, iterator, dataClass);
        } catch (IOException e) {
            throw new ExportFailedException("使用迭代器批量导出到文件失败: " + filePath, e, -1, -1);
        }
    }

    @Override
    public <T> void exportWithIteratorToFile(String filePath, BatchDataIterator<T> iterator, Class<T> dataClass) {
        Pair<String, String> fileNameAndSuffix = getFileNameAndSuffix(filePath);
        ExportParams params = ExportParams.builder()
                .fileName(fileNameAndSuffix.left)
                .suffix(fileNameAndSuffix.right)
                .build();
        exportWithIteratorToFile(filePath, params, iterator, dataClass);
    }

    private <T> void doExportBatch(OutputStream outputStream, ExportParams params, DataSupplier<T> dataSupplier, int batchSize, Class<T> dataClass) throws IOException {
        exportWriter.initialize(outputStream, dataClass);
        Integer sheetMaxRows = getSheetMaxRows(params);
        exportWriter.setSheetMaxRows(sheetMaxRows);

        List<T> batch;
        int offset = 0;
        int totalRows = 0;
        OverflowStrategy strategy = getEffectiveStrategy(params);

        while ((batch = dataSupplier.get(offset, batchSize)) != null && !batch.isEmpty()) {
            if (totalRows + batch.size() > sheetMaxRows) {
                handleBatchOverflow(strategy, totalRows, batch.size(), sheetMaxRows);
            }
            exportWriter.writeBatchWithPaging(batch);
            totalRows += batch.size();
            offset += batchSize;
        }

        exportWriter.close();
    }

    private <T> void doExportWithIterator(OutputStream outputStream, ExportParams params, BatchDataIterator<T> iterator, Class<T> dataClass) throws IOException {
        exportWriter.initialize(outputStream, dataClass);
        Integer sheetMaxRows = getSheetMaxRows(params);
        exportWriter.setSheetMaxRows(sheetMaxRows);

        int totalRows = 0;
        OverflowStrategy strategy = getEffectiveStrategy(params);

        while (iterator.hasNext()) {
            List<T> batch = iterator.next();
            if (batch != null && !batch.isEmpty()) {
                if (totalRows + batch.size() > sheetMaxRows) {
                    handleBatchOverflow(strategy, totalRows, batch.size(), sheetMaxRows);
                }
                exportWriter.writeBatchWithPaging(batch);
                totalRows += batch.size();
            }
        }

        exportWriter.close();
    }

    private <T> void doExportMultiSheet(OutputStream outputStream, ExportParams params, List<SheetData<T>> sheetDataList, Class<T> dataClass) throws IOException {
        exportWriter.initialize(outputStream, dataClass);
        Integer sheetMaxRows = getSheetMaxRows(params);
        exportWriter.setSheetMaxRows(sheetMaxRows);
        // 多sheet页导出时，如果策略为分页，则抛异常
        OverflowStrategy strategy = getEffectiveStrategy(params);
        if (strategy == OverflowStrategy.PAGINATION) {
            throw new IllegalArgumentException("多sheet页导出时，不支持使用分页策略");
        }
        for (SheetData<T> sheetData : sheetDataList) {
            List<T> processedData = handleDataOverflow(sheetData.getData(), strategy, sheetMaxRows);
            exportWriter.writeToSheet(sheetData.getSheetName(), processedData);
        }
        exportWriter.close();
    }

    private <T> void doExportWithIterator(OutputStream outputStream, List<T> data, Class<T> clazz, ExportParams params) throws IOException {
        exportWriter.initialize(outputStream, clazz);
        Integer sheetMaxRows = getSheetMaxRows(params);
        List<T> processedData = handleDataOverflow(data, getEffectiveStrategy(params), sheetMaxRows);
        exportWriter.setSheetMaxRows(sheetMaxRows);
        exportWriter.writeBatchWithPaging(processedData);
        exportWriter.close();
    }

    private void setExportResponse(HttpServletResponse response, ExportParams params) throws IOException {
        response.setContentType("application/vnd.ms-excel");
        String encoding = StringUtils.hasText(params.getEncoding()) ? params.getEncoding() : properties.getDefaultEncoding();
        response.setCharacterEncoding(encoding);

        String fileName = params.getFileName();
        String suffix = StringUtils.hasText(params.getSuffix()) ? params.getSuffix() : properties.getDefaultSuffix();

        String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name())
                .replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encodedFileName + suffix);
    }

    private Pair<String, String> getFileNameAndSuffix(String filePath) {
        String fileName = new File(filePath).getName();
        String suffix = properties.getDefaultSuffix();
        if (fileName.contains(".")) {
            fileName = fileName.substring(0, fileName.lastIndexOf("."));
            suffix = fileName.substring(fileName.lastIndexOf("."));
        }
        return new Pair<>(fileName, suffix);
    }

    private OverflowStrategy getEffectiveStrategy(ExportParams params) {
        return params.getOverflowStrategy() != null ? params.getOverflowStrategy() : properties.getOverflowStrategy();
    }

    private Integer getSheetMaxRows(ExportParams params) {
        return params.getSheetMaxRows() != null ? params.getSheetMaxRows() : properties.getSheetMaxRows();
    }

    private <T> List<T> handleDataOverflow(List<T> data, OverflowStrategy strategy, int sheetMaxRows) {
        if (data.size() <= sheetMaxRows) {
            return data;
        }

        switch (strategy) {
            case PAGINATION:
                // 分页策略时不处理，由ExportWriter处理分页
                return data;
            case TRUNCATE:
                return new ArrayList<>(data.subList(0, sheetMaxRows));
            case THROW_EXCEPTION:
                throw new DataOverflowException(String.format("数据行数(%d)超过单个Sheet页最大行数限制(%d)",
                        data.size(), sheetMaxRows));
            default:
                throw new IllegalArgumentException("不支持的溢出策略: " + strategy);
        }
    }

    private void handleBatchOverflow(OverflowStrategy strategy, int currentRows, int batchSize, int sheetMaxRows) {
        if (strategy == OverflowStrategy.THROW_EXCEPTION) {
            throw new DataOverflowException(String.format("当前数据行数(%d)加上批次数据行数(%d)将超过单个Sheet页最大行数限制(%d)",
                    currentRows, batchSize, sheetMaxRows));
        }
        // 其他策略由ExportWriter处理
    }

    private static class Pair<L,R> {
        public Pair(L left, R right) {
            this.left = left;
            this.right = right;
        }
        public L left;
        public R right;
    }
} 